#include <iostream>
#include <csignal>
#include <vector>
#include <sstream>
#include <thread>
#include <ctime>
#include <cstdlib>
#include <signal.h>
#include <math.h>
#include <time.h>
#include <unistd.h>

#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#include <tlhelp32.h>
#else
#include <sys/wait.h>
#endif

#include "fileutil/fileutil.h"
#include "simpleini/SimpleIni.h"
#include "xorcrypto/xorcrypto.h"
#include "common.h"


#define USE_FORK            (0)
#define ENABLE_CRYPTO_DATA  (0)

// 程序配置信息
#if 0
struct alignas(8) ProgramCfgInfo {
    std::vector<std::string> vtPrograms; // 需要进行看门狗管理的程序集合
    std::string strProgramFolder = ""; // 被监控程序的路径
    uint64_t iCheckIntervalSed = 0; // 程序状态检查时间，单位秒
};
#else
struct ProgramCfgInfo {
    std::vector<std::string> vtPrograms; // 需要进行看门狗管理的程序集合
    std::string strProgramFolder = ""; // 被监控程序的路径
    uint64_t iCheckIntervalSed = 0; // 程序状态检查时间，单位秒
};
#endif

static const char* const EW_CONFIG_FILE = "./configs/watchdog.ini"; // 看门狗模块的配置信息文件
static const char* const CFG_GRP_PROGRAMINFO = "ProgramInfo"; // 程序信息组
static const char* const CFG_KEY_CHECKINTERVAL = "CheckIntervalSed"; // 程序状态检查时间
static const char* const CFG_KEY_PROGRAMFOLDER = "ProgramFolder"; // 被监控程序的路径
static const char* const CFG_KEY_NAMELIST = "NameList"; // 程序集合

static volatile bool gs_bRunStatus = true; // 程序运行标志
static ProgramCfgInfo gs_stProgramCfgInfo; // 程序配置信息
//static volatile int gs_iDayTag = 0; // 记录风险扫描“天”的信息

// 处理系统信号事件
static void pfnSigExit(int signum)
{
    std::signal(signum, SIG_DFL); // 还原默认的信号处理
    gs_bRunStatus = false;
    return;
}

#if 0
static bool isNeedToRiskScan(const std::string& strInputTime)
{
    // 定义输入时间的格式
    const char* format = "%H:%M:%S";

    // 解析输入时间字符串为tm结构体
    struct tm inputTm = {};
    if (strptime(strInputTime.c_str(), format, &inputTm) == nullptr) {
        printf(">>> Input time (%s) format error.\r\n", strInputTime.data());
        return false;
    }

    // 将tm结构体转换为time_t（需补全日期，假设为当天）
    // 注意：strptime不会自动填充年、月、日，需手动设置（此处以当天为例）
    time_t now = time(nullptr);
    struct tm* currentTm = localtime(&now);
    inputTm.tm_year = currentTm->tm_year;
    inputTm.tm_mon = currentTm->tm_mon;
    inputTm.tm_mday = currentTm->tm_mday;

    bool bRet = false;
    if (1 < abs(currentTm->tm_mday - gs_iDayTag)) {
        time_t inputTimestamp = mktime(&inputTm); // 单位秒
        time_t currentTimestamp = mktime(currentTm); // 单位秒
        //printf("%ld, %ld, %lf.\r\n", inputTimestamp, currentTimestamp, fabs(currentTimestamp - inputTimestamp));

        // 比较时间戳，在一定范围内就可认为是有效的，考虑到系统全盘软件扫描可能耗时在2min左右，这里放大时间范围到5min
        if ((5 * 60) > fabs(currentTimestamp - inputTimestamp)) {
            bRet = true;
            // 避免同一天执行多次
            gs_iDayTag = currentTm->tm_mday;
        }
    }

    return bRet;
}
#endif

static void executeProgram(const std::string& strProgramName)
{
    // 子进程执行目标程序
    std::string strFullPath = gs_stProgramCfgInfo.strProgramFolder + strProgramName;
#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    // 创建进程（可指定工作目录、环境变量等）
    if (CreateProcess(strFullPath.data() // 可执行文件路径
                      , nullptr // 命令行参数（可传入参数，如"input.txt"）
                      , nullptr // 进程句柄不可继承
                      , nullptr // 线程句柄不可继承
                      , FALSE // 不继承句柄
                      , 0 // 无特殊创建标志
                      , nullptr // 使用父进程环境
                      , nullptr // 使用父进程当前目录
                      , &si, &pi)) {
        // 等待进程结束
        WaitForSingleObject(pi.hProcess, INFINITE);
        // 关闭句柄
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        printf(">>> It is will execute %s program, fullpath: %s.\r\n", strProgramName.data(), strFullPath.data());
    } else {
        printf(">>> Execute %s failed.\r\n", strFullPath.data());
    }
#else
#if USE_FORK
    // 适用于需长期运行的后台服务（但是fork会阻塞，当前实现逻辑只能启动一个）
    pid_t pid = fork();
    if (pid == 0) {
        setsid(); // 创建新会话，脱离原终端
        signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号

        printf(">>> It is will execute %s program, fullpath: %s.\r\n", strProgramName.data(), strFullPath.data());
        if (-1 == execl(strFullPath.data(), strProgramName.data(), nullptr)) {
            printf(">>> Execute %s program failed.\r\n", strFullPath.data());
        }

        // 或者使用下面逻辑执行程序
        //execvp(strFullPath.data(), nullptr); // execvp 使用传入的程序路径和参数

        exit(-1);
    } else if (pid > 0) {
        // 父进程等待子进程结束
        //waitpid(pid, nullptr, 0);
        int status;
        waitpid(-1, &status, WNOHANG);
    } else {
        printf(">>> Execute %s fork failed.\r\n", strFullPath.data());
    }
#else
    // 调用可执行文件（路径需根据实际调整）
#if 0
    char process[256] = {0};
    //snprintf(process, sizeof (process), "%s &", strFullPath.data());
    snprintf(process, sizeof (process), "nohup %s > /dev/null 2>&1 &", strFullPath.data());
    if (0 != system(process)) {
        printf(">>> Execute %s failed.\r\n", strFullPath.data());
    }
#else
    char cmd[256] = {0};
    snprintf(cmd, sizeof(cmd), "%s &", strFullPath.data());
    system(cmd);
#endif
#endif
#endif
    return;
}

#if USE_FORK
#ifdef __linux__
static void getAllPidsByName(const std::string& strProgramName, std::vector<pid_t>& vtPids)
{
    // 这里需要作特殊处理，因为一般情况下，在Linux系统中，​​进程名称的存储和显示长度由内核直接限制​​，固定为16字节（含'\0'）的硬性限制
    // 当进程名称超过15字节时，top、pgrep等查询到的有效名称为15字节，所以需要特殊处理
    std::string strTempName = strProgramName;
    if (15 < strTempName.length()) {
        strTempName = strTempName.substr(0, 15);
    }

    char cmd[256] = {0};
    snprintf(cmd, sizeof (cmd), "pgrep -d ' ' %s", strTempName.c_str());

    FILE* fp = popen(cmd, "r");
    if (!fp) {
        return;
    }

    char buffer[256] = {0};
    if (fgets(buffer, sizeof(buffer), fp)) {
        std::stringstream ss(buffer);
        std::string strPid = "";
        while (ss >> strPid) {
            vtPids.push_back(atoi(strPid.c_str()));
            strPid = "";
        }
    }
    pclose(fp);

    return;
}
#endif
#endif

static bool isProcessRunning(const std::string& strProcessName)
{
    bool bRet = false;
#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
    // 创建进程快照
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return false;
    }

    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);

    // 遍历进程快照
    if (Process32First(hSnapshot, &pe32)) {
        do {
            // 比较进程名（不区分大小写）
            if (0 == strcmp(pe32.szExeFile, strProcessName.c_str())) {
                bRet = true;
                break;
            }
        } while (Process32Next(hSnapshot, &pe32));
    }

    CloseHandle(hSnapshot);
#else
    // 这里需要作特殊处理，因为一般情况下，在Linux系统中，​​进程名称的存储和显示长度由内核直接限制​​，固定为16字节（含'\0'）的硬性限制
    // 当进程名称超过15字节时，top、pgrep等查询到的有效名称为15字节，所以需要特殊处理
    std::string strTempName = strProcessName;
    if (15 < strTempName.length()) {
        strTempName = strTempName.substr(0, 15);
    }

    char cmd[256] = {0};
    snprintf(cmd, sizeof (cmd), "pgrep -c %s", strTempName.c_str());

    FILE* fp = popen(cmd, "r");
    if (fp) {
        char buffer[32] = {0};
        if (fgets(buffer, sizeof(buffer), fp)) {
            if (0 < atoi(buffer)) {
                bRet = true;
            }
        }
        pclose(fp);
    }
#endif
    return bRet;
}

static void killallPrograms()
{
    for (std::string& strName : gs_stProgramCfgInfo.vtPrograms) {
#if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot == INVALID_HANDLE_VALUE) {
            printf(">>> Failed to create process snapshot.\r\n");
            return;
        }

        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(PROCESSENTRY32);

        if (Process32First(hSnapshot, &pe32)) {
            do {
                if (strName == std::string(pe32.szExeFile)) {
                    HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
                    if (hProcess) {
                        if (TerminateProcess(hProcess, 0)) {
                            printf(">>> Terminate process: %s, pid: %d success.\r\n", pe32.szExeFile, pe32.th32ProcessID);
                        } else {
                            printf(">>> Failed to terminate process: %s.\r\n", pe32.szExeFile);
                        }
                        CloseHandle(hProcess);
                    }
                }
            } while (Process32Next(hSnapshot, &pe32));
        }

        CloseHandle(hSnapshot);
#else
#if USE_FORK
        std::vector<pid_t> pids;
        getAllPidsByName(strName, pids);
        for (pid_t pid : pids) {
            if (0 != kill(pid, SIGTERM)) {
                printf("Terminated process (%s) pid (%d) failed.\r\n", strName.data(), pid);
            }
        }
#else
        if (isProcessRunning(strName)) {
            // 这里需要作特殊处理，因为一般情况下，在Linux系统中，​​进程名称的存储和显示长度由内核直接限制​​，固定为16字节（含'\0'）的硬性限制
            // 当进程名称超过15字节时，top、pgrep等查询到的有效名称为15字节，所以需要特殊处理
            std::string strTempName = strName;
            if (15 < strTempName.length()) {
                strTempName = strTempName.substr(0, 15);
            }

            char cmd[256] = {0};
            snprintf(cmd, sizeof(cmd), "killall %s", strTempName.c_str());
            system(cmd);
        }
#endif

#endif
    }

    return;
}

static void checkAllPrograms()
{
    for (std::string& strName : gs_stProgramCfgInfo.vtPrograms) {
        if (!isProcessRunning(strName)) {
            executeProgram(strName);
            std::this_thread::sleep_for(std::chrono::seconds(1)); // 等待程序被正常启动
        }
    }

    return;
}

static void pfnCheckProgramStatus()
{
    printf(">>> Enter in program status check thread.\r\n");

    uint64_t start = 0, current = 0, interval = 0;

    while (gs_bRunStatus) {
        current = getCurSystemMsTime(); // 当前计时
        interval = current - start;
        if (interval >= gs_stProgramCfgInfo.iCheckIntervalSed) {
            // 检查程序运行状态
            checkAllPrograms();
            // 更新时间
            start = current;
        }

        //std::this_thread::yield();
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }

    printf(">>> Exit to program status check thread.\r\n");

    return;
}

bool readSoftwareConfig()
{
    CSimpleIniA objIni;
    // 从配置文件中读取配置信息
    if (ENABLE_CRYPTO_DATA) {
        // 配置文件加密了，则需要先读取文件，解密后进行配置解析
        std::string strContent("");
        readFile(EW_CONFIG_FILE, strContent); // 读取文件内容
        if (strContent.empty()) {
            printf(">>> Read config file (%s) failed.\r\n", EW_CONFIG_FILE);
            return false;
        }

        // 解密文件内容
        size_t sDataLen = strContent.size();
        char *pDeContent = new (std::nothrow) char[sDataLen];
        if (!pDeContent) {
            printf(">>> Alloc data stack space failed.\r\n");
            return false;
        }

        if (0 != XORCryptoByDefaultKey(strContent.data(), static_cast<unsigned int>(sDataLen), pDeContent, static_cast<unsigned int>(sDataLen))) {
            printf(">>> Decrypto config file (%s) content failed.\r\n", EW_CONFIG_FILE);
            if (pDeContent) {
                delete [] pDeContent;
                pDeContent = nullptr;
            }

            return false;
        }
        // 更新解密后的内容
        strContent = std::string(pDeContent, sDataLen);
        if (pDeContent) {
            delete [] pDeContent;
            pDeContent = nullptr;
        }

        // 加载解密内容
        if (0 > objIni.LoadData(strContent)) {
            printf(">>> Load config file (%s) content failed.\r\n", EW_CONFIG_FILE);
            return false;
        }

    } else {
        // 配置文件未加密，直接读取文件内容，解析配置
        if (0 > objIni.LoadFile(EW_CONFIG_FILE)) {
            printf(">>> Load config file (%s) failed.\r\n", EW_CONFIG_FILE);
            return false;
        }
    }

    // 解析配置
    gs_stProgramCfgInfo.iCheckIntervalSed = std::stoull(objIni.GetValue(CFG_GRP_PROGRAMINFO, CFG_KEY_CHECKINTERVAL)) * 1000; // s转换为ms
    gs_stProgramCfgInfo.strProgramFolder = std::string(objIni.GetValue(CFG_GRP_PROGRAMINFO, CFG_KEY_PROGRAMFOLDER));
    printf(">>> Watchdog read config information: %d, path: %s.\r\n", gs_stProgramCfgInfo.iCheckIntervalSed, gs_stProgramCfgInfo.strProgramFolder.data());

    // 读取需要看门狗进行管理的应用配置
    const char* pProgramNameList = objIni.GetValue(CFG_GRP_PROGRAMINFO, CFG_KEY_NAMELIST);
    if (pProgramNameList) {
        std::istringstream stream(pProgramNameList);
        std::string name = "";
        while (std::getline(stream, name, ',')) {
            gs_stProgramCfgInfo.vtPrograms.emplace_back(name);
            name = "";
        }
    }

    return true;
}

int main(int argc, char **argv)
{
    (void)argc;
    (void)argv;

    // 注册系统事件信号
    std::signal(SIGINT, pfnSigExit);
    std::signal(SIGFPE, pfnSigExit);
    std::signal(SIGSEGV, pfnSigExit);
    std::signal(SIGTERM, pfnSigExit);

    if (!readSoftwareConfig()) {
        printf(">>> Read software config failed, exit.\r\n");
        return -1;
    }

    // 首先关闭之前所有的应用进程
    killallPrograms();

    printf("[%s]>>> ESS Watchdog program is started.\r\n", getFormatCurSystemTime().data());
    std::thread thStatusCheck(pfnCheckProgramStatus);

    while (gs_bRunStatus) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    if (thStatusCheck.joinable()) {
        thStatusCheck.join();
    }

    killallPrograms();

    printf("[%s]>>> ESS Watchdog program is stoped.\r\n", getFormatCurSystemTime().data());

    /*************************************************************************
     * 还有使用superisor辅助进行实现的方案，ubuntu中安装：sudo apt install supervisor -y
     * supervisor.startProcess、supervisor.stopProcess
     * ***********************************************************************/

    return 0;
}
